package ua.lviv.franko.solvers.stressanalysis.twoDimention.savestructures.bounderyconditions;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import ua.lviv.franko.femShapeFunction2D.FEMShapeFunction1;
import ua.lviv.franko.integration.Gauss2D;
import ua.lviv.franko.integration.IFunction2D;
import ua.lviv.franko.matrix.hash.lu.HashMatrixLUStyle;
import ua.lviv.franko.triangulation.SaveStructures.Node;
import ua.lviv.franko.triangulation.SaveStructures.NodePair;

public class BounderyConditionLinear implements BounderyConditionStressInterface {
	private static final int		NUMBER_OF_UNKNOWNS	= 2;

	protected BounderyConditionType	conditionType;
	protected IFunction2D			bounderyValue1;
	protected IFunction2D			bounderyValue2;

	protected List<NodePair>		nodePairs;

	protected Gauss2D				quadrature;

	public BounderyConditionLinear(BounderyConditionType conditionType, IFunction2D bounderyValue1, IFunction2D bounderyValue2, List<NodePair> nodePairs, Gauss2D quadrature) {
		super();
		this.conditionType = conditionType;
		this.bounderyValue1 = bounderyValue1;
		this.bounderyValue2 = bounderyValue2;
		this.nodePairs = nodePairs;
		this.quadrature = quadrature;
	}
	
	public void setBounderyValue1(IFunction2D bounderyValue1) {
		this.bounderyValue1 = bounderyValue1;
	}
	
	public void setBounderyValue2(IFunction2D bounderyValue2) {
		this.bounderyValue2 = bounderyValue2;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ua.lviv.franko.solvers.stressanalysis.twoDimention.savestructures.
	 * bounderyconditions
	 * .BounderyConditionStressInterface#applyBounderyCondition
	 * (ua.lviv.franko.HashMatrix.HashMatrix, double[])
	 */
	@Override
	public void applyBounderyCondition(HashMatrixLUStyle matrix, double[] vector) {
		switch (conditionType) {
			case DIRIHLET_TYPE:
				Set<Node> alreadyApplyedNodes = new HashSet<Node>();
				for (NodePair pair : nodePairs) {
					applyDirihlet(pair.getA(), matrix, vector, alreadyApplyedNodes);
					applyDirihlet(pair.getB(), matrix, vector, alreadyApplyedNodes);
				}

				break;
			case NAYMAN_TYPE:
				for (NodePair pair : nodePairs) {
					Node A = pair.getA();
					Node B = pair.getB();
					Node C = new Node(A.x + B.x, A.y + B.y, false, -1);
					final FEMShapeFunction1 fA = new FEMShapeFunction1(A, B, C);
					fA.setNodeNum(1);
					applyNayman(A, A, B, vector, fA);
					fA.setNodeNum(2);
					applyNayman(B, A, B, vector, fA);
				}
				break;

			default:
				break;
		}
	}

	protected void applyDirihlet(Node A, HashMatrixLUStyle matrix, double[] vector, Set<Node> alreadyApplyedNodes) {
		if (!alreadyApplyedNodes.contains(A)) {
			// clearing unnecessary data
			matrix.getJRowIndex(A.Number * NUMBER_OF_UNKNOWNS).clear();
			matrix.getJRowValues(A.Number * NUMBER_OF_UNKNOWNS).clear();
			matrix.getJRowIndex(A.Number * NUMBER_OF_UNKNOWNS + 1).clear();
			matrix.getJRowValues(A.Number * NUMBER_OF_UNKNOWNS + 1).clear();

			// setting values
			matrix.setValue(A.Number * NUMBER_OF_UNKNOWNS, A.Number * NUMBER_OF_UNKNOWNS, 1.0);
			vector[A.Number * NUMBER_OF_UNKNOWNS] = bounderyValue1.calculate(A.x, A.y);

			matrix.setValue(A.Number * NUMBER_OF_UNKNOWNS + 1, A.Number * NUMBER_OF_UNKNOWNS + 1, 1.0);
			vector[A.Number * NUMBER_OF_UNKNOWNS + 1] = bounderyValue2.calculate(A.x, A.y);
		}
	}

	protected void applyNayman(Node applyToNode, Node A, Node B, double[] vector, final FEMShapeFunction1 fA) {
		vector[A.Number * NUMBER_OF_UNKNOWNS] += quadrature.gaussOnGamma(A, B, new IFunction2D() {

			@Override
			public double calculate(double x, double y) {
				return fA.calculate(x, y) * bounderyValue1.calculate(x, y);
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				return 0;
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				return 0;
			}
		});
		vector[A.Number * NUMBER_OF_UNKNOWNS + 1] += quadrature.gaussOnGamma(A, B, new IFunction2D() {

			@Override
			public double calculate(double x, double y) {
				return fA.calculate(x, y) * bounderyValue2.calculate(x, y);
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				return 0;
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				return 0;
			}
		});
	}
	
	@Override
	public List<NodePair> getNodePairs() {
		return nodePairs;
	}
}
